import pandas as pd
import pandas_profiling
import numpy as np
from sklearn.model_selection import train_test_split, GridSearchCV, StratifiedKFold
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.metrics import explained_variance_score, mean_squared_error, classification_report, confusion_matrix
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier, export_graphviz
from sklearn.externals.six import StringIO
from sklearn.manifold import TSNE
from sklearn.neural_network import MLPClassifier
from sklearn.cluster import KMeans, SpectralClustering
from sklearn.decomposition import PCA
import pydot
from IPython.display import Image
import matplotlib.pyplot as plt
import time
import seaborn as sn
# In order to see full screen (horizontal scrolling) dataframes
from IPython.display import display
pd.options.display.max_columns = None
Die Datensätze salaries und players wurden aus folgender Quelle gewählt: https://data.world/datadavis/nba-salaries.
Der Datensatz seasons_stats wurde aus folgender Quelle gewählt: https://www.kaggle.com/drgilermo/nba-players-stats
salaries_df = pd.read_csv('data/salaries.csv')
players_df = pd.read_csv('data/players.csv')
seasons_stats_df = pd.read_csv('data/seasons_stats.csv')
Diese Daten von https://data.world/datadavis/nba-salaries bilden den Ausgangspunkt für die beschriebene Fragestellung. Der Datensatz beinhaltet im Wesentlichen das ausbezahlte Gehalt an einen Spieler für eine bestimmte Saison.
salaries_df.sample()
Der nachfolgende Pandas Profiling Report verschafft einen vollständigen Überblick (Statistiken, Visualisierungen) über den importierten Datensatz. Die Datei _salaries_profilereport zeigt das Ergebnis des Reports.
# salaries_profile_report = salaries_df.profile_report()
# salaries_profile_report.to_file(output_file="salaries_profile_report.html")
salaries_df.columns
Laut dem Pandas Profiling Report handelt es sich bei der Variable league um eine Konstante. Alle Spieler des Datensatzes sind in der NBA tätig. Das Feature schafft somit keinen Mehrwert und kann entfernt werden.
salaries_df['league'].describe()
salaries_df.drop(columns=['league'], inplace=True)
Die Variable player_id kann zunächst im Datensatz gelassen werden. Über diese ID können später die zusätzlichen Daten des Spielers vom Datensatz Players eingebunden werden.
Die wichtigste Variable salary wird natürlich im Datensatz behalten.
Die Variablen season, season_start und season_end müssen nicht alle im Datensatz belassen werden. Die Variable season kann aus den beiden anderen konstruiert werden und ist somit redundant. Sie kann somit entfernt werden.
salaries_df.drop(columns=['season'], inplace=True)
salaries_df.head()
salaries_df.tail()
salaries_df.sample(7)
salaries_df.describe()
salaries_df.info()
Die Daten von https://data.world/datadavis/nba-salaries komplementieren den zuvor beschriebenen Salaries Datensatz. Sie beinhalten im Wesentlichen Informationen zu den jeweiligen Spielern.
players_df.sample()
Der nachfolgende Pandas Profiling Report verschafft einen vollständigen Überblick (Statistiken, Visualisierungen) über den importierten Datensatz. Die Datei _players_profilereport zeigt das Ergbnis des Reports.
# players_profile_report = players_df.profile_report()
# players_profile_report.to_file(output_file="players_profile_report.html")
players_df.columns
Die Variable id kann zunächst im Datensatz belassen werden. Mit dieser Variable kann nämlich später das Matching mit dem Salaries Datensatz vollzogen werden.
Laut dem Pandas Profiling Report weißt die Variable birthDate folgende Probleme auf:
Außerdem hat die Variable 28 fehlende Datenpunkte.
Prinzipiell könnten die Features _birthmonth, _birthday und _birthyear aus der genannten Variable entnommen werden. Dies scheint auf den ersten Blick jedoch keinen wirklichen Mehrwert zu bieten. Somit wird dieses Feature verworfen.
players_df.drop(columns=['birthDate'], inplace=True)
Laut dem Pandas Profiling Report weißt die Variable birthPlace folgende Probleme auf:
Die fehlenden Daten werden mit dem Top-Wert aufgefüllt.
players_df['birthPlace'].describe()
players_df['birthPlace'] = players_df['birthPlace'].fillna("Chicago, Illinois")
Um dieses Problem zu lösen soll nur das Land in Betracht gezogen werden. Eine drastischere Maßnahme wäre die reine Verwendung des Features born_in_usa. Diese Variable wurde nur Aufschluss darüber geben, ob der Spieler in der USA geboren wurde oder nicht.
players_df['birthPlace'].describe()
Wie man oberhalb erkennen kann weißt das Feature 1632 einmalige Werte auf.
def generalizeBirthPlace(birthPlace):
splitted = birthPlace.split(",")
return splitted[1] if len(splitted) >= 2 else ",".join(splitted)
def statesToUSA(birthPlace):
states = ["Alabama","Alaska","Arizona","Arkansas","California","Colorado","Connecticut","Delaware","Florida","Georgia","Hawaii","Idaho","Illinois",
"Indiana","Iowa","Kansas","Kentucky","Louisiana","Maine","Maryland","Massachusetts","Michigan","Minnesota","Mississippi","Missouri","Montana",
"Nebraska","Nevada","New Hampshire","New Jersey","New Mexico","New York","North Carolina","North Dakota","Ohio","Oklahoma","Oregon","Pennsylvania",
"Rhode Island","South Carolina","South Dakota","Tennessee","Texas","Utah","Vermont","Virginia","Washington","West Virginia","Wisconsin","Wyoming"]
def process(string):
return string.lower().strip()
states = map(process, states)
return "USA" if process(birthPlace) in states else birthPlace
players_df['birthPlace'] = players_df['birthPlace'].apply(generalizeBirthPlace)
players_df['birthPlace'] = players_df['birthPlace'].apply(statesToUSA)
players_df.rename(columns={"birthPlace": "birth_country"}, inplace=True)
players_df['birth_country'].sample(7)
players_df['birth_country'].describe()
Durch die reine Verwendung des Landes lassen sich 1632 einmalige Werte auf 90 reduzieren.
players_df['born_in_usa'] = np.where(players_df['birth_country'] == 'USA', True, False)
players_df['born_in_usa'].describe()
Alle Variablen die Statistiken zu der gesamten Karriere des Spielers beinhalten können verworfen werden. Diese Features würden keinen Mehrwert für das aktuelle Gehalt in dieser Saison bieten. Stattdessen sollen im weiteren Verlauf die jeweiligen Saisonstatistiken des Spielers durch einen weiteren Datensatz erweitert werden.
players_df.drop(columns=['career_AST', 'career_FG%', 'career_FG3%', 'career_FT%', 'career_G', 'career_PER', 'career_PTS', 'career_TRB', 'career_WS', 'career_eFG%'], inplace=True)
Laut dem Pandas Profiling Report weißt die Variable college folgende Probleme auf:
Das Problem der fehlenden Datenpunkte wird durch folgende Annahme behoben: Fehlende Datenpunkte repräsentieren Spieler, die kein College besucht haben. LeBron James hat beispielsweise kein College eingetragen und auch tatsächlich kein College besucht. Deshalb wird der Wert _NOCOLLEGE bei diesen Spielern hinzugefügt.
players_df['college'] = players_df['college'].fillna('NO_COLLEGE')
players_df['college'].describe()
Das Problem der hohen Kardinalität scheint derzeit nicht lösbar zu sein. Eine drastische Maßnahme wäre ausschließlich das Feature attended_college aufzunehmen.
players_df['attended_college'] = np.where(players_df['college'] == 'NO_COLLEGE', False, True)
players_df['attended_college'].describe()
Laut dem Pandas Profiling Report weißen die Features draft_pick, draft_round, draft_team, draft_year folgende Probleme auf:
Diese Probleme sollen durch die Extrahierung eines neuen Features drafted_player behoben werden. Die ursprünglichen Features werden verworfen. Das neue Feature beruht auf der Annahme, dass es sich bei diesen Spielern um undrafted players handelt. Da es diese Kategorie im Datensatz nicht gibt und diese Art von Spielern jedoch sehrwohl existieren wird in dieser Analyse von diesem Umstand ausgegangen.
players_df['drafted_player'] = np.where(
players_df['draft_pick'].isna() |
players_df['draft_round'].isna() |
players_df['draft_team'].isna() |
players_df['draft_year'].isna(), True, False
)
players_df['drafted_player'].describe()
Die originalen Features werden nicht benötigt und können vom Datensatz entfernt werden.
players_df.drop(columns=['draft_pick', 'draft_round', 'draft_team', 'draft_year'], inplace=True)
Bei der Variable height handelt es sich derzeit um kategorische Daten. Die Größe soll daher in eine numerische Repräsentation umgewandelt werden. Im originalen Datensatz handelt es sich um eine Foot-Inch Darstellung. Daher wird die Größe auf Inches umgerechnet.
def heightToInches(height):
foot_and_inches = height.split('-')
foot_in_inches = int(foot_and_inches[0]) * 12
inches = int(foot_and_inches[1])
return foot_in_inches + inches
players_df['height'].describe()
players_df['height'] = players_df['height'].apply(heightToInches)
players_df.rename(columns={"height": "height_in_inches"}, inplace=True)
players_df['height_in_inches'].sample(7)
Die Variable highSchool weißt Ähnlichkeiten mit dem Feature college auf:
Da dieses Feature eine wirkliche hohe Kardinalität besitzt soll ausschließlich das Feature attended_high_school extrahiert werden.
players_df['highSchool'].describe()
players_df['highSchool'] = players_df['highSchool'].isna()
players_df.rename(columns={"highSchool": "attended_high_school"}, inplace=True)
players_df['attended_high_school'].sample(7)
Laut dem Pandas Profiling Report weißt die Variable name folgende Probleme auf:
Möglicherweise könnte man name_length aus dem genannten Feature extrahieren, jedoch besteht die Vermutung, dass der Name keinen großen Einfluss auf das Gehalt der Spieler hat. Deshalb soll das Feature aus dem Datensatz entfernt werden. Vorerst wird es jedoch beibehalten, da es später für ein Matching mit einem anderen Datensatz verwendet wird.
Laut dem Pandas Profiling Report weißt die Variable position keine hohe Kardinalität auf. Dennoch wird versucht die möglichen Ausprägungen etwas einzuschränken. Spieler denen mehrer Positionen zugeordnet sind bekommen einen dementsprechenden Eintrag. Eine drastischere Vorgehensweise wäre die reine Verwendung des Features multiple_positions.
def processMultiplePositions(position):
return "MULTIPLE_POSITIONS" if "and" in position.split() else position
players_df['position'].describe()
players_df['position'] = players_df['position'].apply(processMultiplePositions)
players_df['position'].describe()
players_df['position'].sample(7)
players_df['multiple_positions'] = np.where(players_df['position'] == "MULTIPLE_POSITIONS", True, False)
players_df['multiple_positions'].describe()
Die Variable shoots kann vorerst ohne weitere Bearbeitung übernommen werden.
Laut dem Pandas Profiling Report weißt die Variable weight folgendes Problem auf:
Derzeit handelt es sich beim Gewicht um einen kategorischen Wert. Das Gewicht soll daher in eine numerische Repräsentation umgewandelt werden. Somit wird die Einheit wird entfernt und der Datentyp angepasst.
Laut dem Pandas Profiling Report hat diese Variable 5 fehlende Datenpunkte. Diese werden durch den Top-Wert aufgefüllt.
players_df['weight'].describe()
players_df['weight'] = players_df['weight'].fillna('210lb')
players_df['weight'].describe()
def removeLB(weight):
return weight[:-2]
players_df['weight'] = players_df['weight'].apply(removeLB)
players_df['weight'] = players_df['weight'].astype(int)
players_df['weight'].sample(7)
players_df.head()
players_df.tail()
players_df.sample(7)
players_df.describe()
players_df.info()
Die Daten von https://www.kaggle.com/drgilermo/nba-players-stats sollen als Erweiterung des Salaries Datensatz fungieren. Wie bereits erwähnt, sind die Career Stats des Players Datensatzes eher unbrauchbar. Es macht vermutlich mehr Sinn, dass jeweilige Gehalt in einer Saison durch die entsprechenden _seasonsstats zu erweitern. Dies soll durch diesen Datensatz ermöglicht werden. Er beinhaltet im Wesentlichen Statistiken von einem Spieler in einer Saison.
seasons_stats_df.sample()
seasons_stats_df.columns
Die erste Variable unnamed scheint einen Index darzustellen. Sie kann ohne weitere Bedenken entfernt werden.
seasons_stats_df.drop(seasons_stats_df.columns[0], axis=1, inplace=True)
Die Variablen year und player sollen für das Matching mit den anderen Datensätzen verwendet werden. Die Variable year kann mit season_end des Salaries Datensatzes verbunden werden.
Dieser Datensatz beinhaltet ebenfalls eine Variable Pos (Position). Diese kennzeichnet jedoch die Position in dieser Saison. Daher wird dieses feature und nicht das Feature des anderen Datensatzes verwendet. Die erstellten Features des anderen Datensatzes können somit zunächst verworfen werden.
seasons_stats_df['Pos'].describe()
players_df.drop(columns=['position', 'multiple_positions'], inplace=True)
Das Alter des jeweiligen Spielers scheint auf jeden Fall Sinn zu machen. Daher wird dieses Feature behalten.
seasons_stats_df['Age'].describe()
Alle Saisonstatistiken des Spielers sollen natürlich behalten werden. Der Datensatz soll genau diese Erweiterung an Daten liefern.
seasons_stats_df.head()
seasons_stats_df.tail()
seasons_stats_df.sample(7)
seasons_stats_df.describe()
seasons_stats_df.info()
Es gibt anscheinend Spieler, die innerhalb einer Saison bei mehreren Vereinen gespielt haben. In diesem Fall wird nur der letzte Aufenthalt des Spielers (der letzte Eintrag) berücksichtigt.
seasons_stats_df.loc[seasons_stats_df['Player'] == "Ed Bartels"]
duplicated_indices = seasons_stats_df[seasons_stats_df.duplicated(['Player', 'Year'], keep='last')].index
seasons_stats_df.drop(duplicated_indices, inplace=True)
df = pd.merge(salaries_df, players_df, left_on='player_id', right_on='_id')
df.drop(columns=['_id', 'player_id'], inplace=True)
df.sample(7)
df = pd.merge(df, seasons_stats_df, left_on=['name', 'season_end'], right_on=['Player', 'Year'])
seasons_stats_df[seasons_stats_df['Player'] == "LeBron James"].head(1)
df.loc[df['Player'] == "LeBron James"].head(1)
df.drop(columns=['name', 'Player', 'Year'], inplace=True)
df.head()
df.tail()
df.sample(7)
df.describe()
df.info()
Durch den zuvor erlangten Überblick der Splaten lassen sich zwei Spalten ohne Datenpunkte erkennen. Diese sollen entfernt werden.
df.drop(columns=['blanl', 'blank2'], inplace=True)
Die Spalte, wo die Namen der Teams länger sind sollen entfernt werden. Die andere Spalte mit den kürzeren Namen wird umbenannt. Dadurch sollen die resultierenden Spaltennamen beim One Hot Encoding etwas schöner werden.
df.drop(columns=['team'], inplace=True)
df.rename(columns={"Tm": "team"}, inplace=True)
Gewisse Spaltennamen müssen noch unbenannt werden. Nun sollten alle Features, abgesehen von den Saisonstatistiken k
df.rename(columns={"Pos": "position", "Age": "age"}, inplace=True)
df.info()
df.isnull().sum().sort_values(ascending=False)
Es existieren nur fehlende Daten in den Saisonstatistiken. Diese werden mit dem Durchschnittswert befüllt.
features_with_missing_data = [
"3P%",
"FT%",
"2P%",
"FTr",
"FG%",
"eFG%",
"3PAr",
"TS%",
"TOV%",
"ORB%",
"TRB%",
"DRB%",
"WS/48",
"BLK%",
"PER",
"STL%",
"USG%",
"AST%",
]
for feature in features_with_missing_data:
df[feature] = df[feature].fillna(df[feature].mean())
df.isnull().sum().sort_values(ascending=False)
Der Linear Regresion Store wurde als erste Instanz für die Feature Auswahl verwendet.
df.select_dtypes('bool').columns.values
df.select_dtypes('object').columns.values
df = pd.get_dummies(df, columns=['attended_high_school'])
df = pd.get_dummies(df, columns=['drafted_player'])
df = pd.get_dummies(df, columns=['shoots'])
Ohne Verwendung von diesem Feature würde der Linear Regression Score etwas veringert sein.
# df.drop(columns=['position'], inplace=True)
df = pd.get_dummies(df, columns=['position'])
Ohne Verwendung von diesem Feature würde der Linear Regression Score etwas veringert sein.
# df.drop(columns=['team'], inplace=True)
df = pd.get_dummies(df, columns=['team'])
Die Verwendung von diesem Feature resultiert in einem negativen Linear Regression Score. Das Feature kann somit ignoriert werden.
college_feature = df.college
df.drop(columns=['college'], inplace=True)
# df = pd.get_dummies(df, columns=['college'])
# df.drop(columns=['attended_college'], inplace=True)
Die Verwendung von diesem Feature resultiert in einer 1-2% Steigerung des Linear Regression Score. Das Feature kann somit verwendet werden.
# df.drop(columns=['attended_college'], inplace=True)
df = pd.get_dummies(df, columns=['attended_college'])
# df.drop(columns=['college'], inplace=True)
Die Verwendung von diesem Feature resultiert in einem negativen Linear Regression Score. Das Feature kann somit ignoriert werden.
df.drop(columns=['birth_country'], inplace=True)
# df = pd.get_dummies(df, columns=['birth_country'])
# df.drop(columns=['born_in_usa'], inplace=True)
Die Verwendung von diesem Feature führt zu einer minimalen Verringerung (~0.02%) des Linear Regression Score. Es liefert somit jedoch keinen Mehrwert und kann somit ignoriert werden.
df.drop(columns=['born_in_usa'], inplace=True)
# df = pd.get_dummies(df, columns=['born_in_usa'])
# df.drop(columns=['birth_country'], inplace=True)
df.head()
df.tail()
df.sample(7)
df.describe()
df.info()
df.columns
correlations_df = pd.DataFrame(abs(df.corr().salary).sort_values(ascending = False))
correlations_df.head(10)
top_10_correlations = [
"WS",
"VORP",
"PTS",
"DRB",
"FGA",
"FG",
"OWS",
"FT",
"FTA"
]
correlations_df.head(20)
df.salary.describe()
print(df.salary.min())
print(df.salary.max())
print(df.salary.mean())
df.salary.hist(bins=20, alpha=0.5)
# categorical_salary_labels = ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"]
categorical_salary_labels = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
categorical_salary_bins = 10
df["categorical_salary"] = pd.qcut(df.salary, q=categorical_salary_bins, labels=categorical_salary_labels)
df["categorical_salary"].unique()
df["categorical_salary"].describe()
Vergleich der Datenaufteilung in den einzelnen Bins.
for label in categorical_salary_labels:
print(df.loc[df["categorical_salary"] == label].size)
split_df = df
X = split_df.drop(columns = ["salary", "categorical_salary"])
Die zuvor berechneten top 10 Correlation Features zu nehmen würde in einem schlechteren Linear Regression Score resultieren.
# X = X[top_10_correlations]
Die später berechneten top 30 Linear Regression Features zu nehmen würde in einem schlechteren Linear Regression Score resultieren.
"""
top_30 = [
'TRB', 'PTS', 'DRB', 'drafted_player_False', 'drafted_player_True',
'FG', 'FGA', '2PA', 'attended_college_False',
'attended_college_True', 'ORB', 'FT', '3PA',
'attended_high_school_False', 'attended_high_school_True', '3P',
'2P', 'position_PF', 'shoots_Right', 'shoots_Left', 'position_C',
'position_PG', 'position_SF', 'position_SG', 'team_LAC',
'team_CLE', 'team_PHO', 'team_IND', 'team_GSW', 'team_NYK'
]
X = X[top_30]
"""
Die ausschließliche Verwendung der Saisonstatistiken würde in einem schlechteren Linear Regression Score resultieren.
statistics = [
"3P%",
"FT%",
"2P%",
"FTr",
"FG%",
"eFG%",
"3PAr",
"TS%",
"TOV%",
"ORB%",
"TRB%",
"DRB%",
"WS/48",
"BLK%",
"PER",
"STL%",
"USG%",
"AST%",
"MP",
"GS",
"G",
"PTS",
"PF",
"OWS",
"TOV",
"BLK",
"STL",
"AST",
"TRB",
"DRB",
"ORB",
"FTA",
"FT",
"2PA",
"2P",
"3PA",
"3P",
"FGA",
"FG",
"VORP",
"BPM",
"DBPM",
"OBPM",
"WS",
"DWS"
]
# X = X[statistics]
Das Verwerfen der folgenden Features führt zu leichten Verbesserungen des Linear Regression Score.
X.drop(columns = ["season_start"], inplace=True)
X.drop(columns = ["3P%"], inplace=True)
Die Verwendung des categorical_salary hat im Allgemeinen zu besseren Ergebnissen geführt.
# y = split_df["salary"]
y = split_df["categorical_salary"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
# X = ((X-X.min())/(X.max()-X.min()))
sc = StandardScaler()
X_train_std = sc.fit_transform(X_train)
X_test_std = sc.transform(X_test)
# With StandardScaler
X_train_linear = X_train_std
X_test_linear = X_test_std
# Without StandardScaler
# X_train_linear = X_train
# X_test_linear = X_test
y_train_linear = y_train
y_test_linear = y_test
linreg_model = LinearRegression().fit(X_train_linear, y_train_linear)
linreg_predictions = linreg_model.predict(X_test_linear)
for i, prediction in enumerate(linreg_predictions[:10]):
print('real: {} -- pred: {}'.format(np.array(y_test_linear)[i], prediction))
data = pd.DataFrame(abs(df.corr().salary).sort_values(ascending = False)).head(10)
data.head(10)
data.tail(10)
zipped = list(zip(X_train.columns, linreg_model.coef_))
data = pd.DataFrame(zipped, columns=['feature', 'coef'])
data = data.reindex(data.coef.abs().sort_values(ascending = False).index)
data.head(10)
data.tail(10)
def print_outcome_for_linear():
score_result = linreg_model.score(X_test_linear, y_test_linear)
print('coefficient of determination:', score_result, '\n')
explained_variance_score_result = round(explained_variance_score(np.array(y_test_linear), linreg_predictions) * 100, 2)
print('explained variance score in %:', explained_variance_score_result, '\n')
mean_squared_error_result = mean_squared_error(np.array(y_test_linear), linreg_predictions)
print('mean squared error:', mean_squared_error_result, '\n')
print_outcome_for_linear()
X_train_logistic = X_train_std
X_test_logistic = X_test_std
y_train_logistic = y_train
y_test_logistic = y_test
logreg_model = LogisticRegression(C=1.0, solver='lbfgs', multi_class='ovr', max_iter=1000).fit(X_train_logistic, y_train_logistic)
logreg_predictions = logreg_model.predict(X_test_logistic)
for i, prediction in enumerate(logreg_predictions[:10]):
print('real: {} -- pred: {}'.format(np.array(y_test_logistic)[i], prediction))
def print_outcome_for_logistic():
score_result = logreg_model.score(X_test_logistic, y_test_logistic)
print('coefficient of determination:', score_result, '\n')
explained_variance_score_result = round(explained_variance_score(np.array(y_test_logistic), logreg_predictions) * 100, 2)
print('explained variance score in %:', explained_variance_score_result, '\n')
mean_squared_error_result = mean_squared_error(np.array(y_test_logistic), logreg_predictions)
print('mean squared error:', mean_squared_error_result, '\n')
print_outcome_for_logistic()
X_train_knn = X_train_std
X_test_knn = X_test_std
y_train_knn = y_train
y_test_knn = y_test
Die besten Settings wurden mit dem salary als SUT ermittelt. Die Durchlaufzeit verhindert ein Austesten von vielen verschiedenen Setups.
Durch die Verwendung von GridSearch (https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html) ist Cross Validation beispielsweise schon inkludiert.
first = [50, 75, 100, 125, 150, 175, 200]
second = [100, 112, 125, 127, 150]
third = [100, 106, 112, 119, 125]
fourth = [111, 112, 113]
fifth = [112, 113, 114, 115]
sixth = [113]
def do_grid_search_for_knn():
param_grid = {
'n_neighbors': sixth,
'metric': ['euclidean', 'manhattan'],
'weights': ['uniform', 'distance'],
}
clf = GridSearchCV(KNeighborsClassifier(), param_grid, verbose = 1, cv = 3, n_jobs = -1)
return clf.fit(X_train_knn, y_train_knn)
grid_knn = do_grid_search_for_knn()
def print_outcome_for_knn():
print('best params:\n', grid_knn.best_params_, '\n')
print('best score:\n', grid_knn.best_score_, '\n')
print('best score (%):\n', round(grid_knn.best_score_ * 100, 2), '\n')
print_outcome_for_knn()
# pd.DataFrame(grid_knn.cv_results_)
https://towardsdatascience.com/the-basics-knn-for-classification-and-regression-c1e8a6c955
One other issue with a KNN model is that it lacks interpretability. An OLS linear regression will have clearly interpretable coefficients that can themselves give some indication of the ‘effect size’ of a given feature (although, some caution must taken when assigning causality). Asking which features have the largest effect doesn’t really make sense for a KNN model, however. Partly because of this, KNN models also can’t really be used for feature selection, in the way that a linear regression with an added cost function term, like ridge or lasso, can be, or the way that a decision tree implicitly chooses which features seem most valuable.
X_train_dt = X_train_std
X_test_dt = X_test_std
y_train_dt = y_train
y_test_dt = y_test
Die besten Settings wurden mit dem salary als SUT ermittelt. Die Durchlaufzeit verhindert ein Austesten von vielen verschiedenen Setups.
Durch die Verwendung von GridSearch (https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html) ist Cross Validation beispielsweise schon inkludiert.
def do_grid_search_for_dt():
param_grid = {
# 'max_depth': range(1,30),
# 'criterion': ["entropy", "gini"]
'max_depth': [7], # Best Settings
'criterion': ["entropy"] # Best Settings
}
clf = GridSearchCV(DecisionTreeClassifier(), param_grid=param_grid, verbose=False, cv=StratifiedKFold(n_splits=20, random_state=15, shuffle=True) ,n_jobs = -1)
return clf.fit(X_train_dt, y_train_dt)
grid_dt = do_grid_search_for_dt()
def print_outcome_for_dt():
print('best params:\n', grid_dt.best_params_, '\n')
print('best score:\n', grid_dt.best_score_, '\n')
print('best score (%):\n', round(grid_dt.best_score_ * 100, 2), '\n')
print('best estimator:\n', grid_dt.best_estimator_, '\n')
print_outcome_for_dt()
column_names = X_train.columns
dot_data = StringIO()
export_graphviz(grid_dt.best_estimator_, out_file=dot_data,
feature_names=column_names,
filled=True, rounded=True,
proportion=True,
special_characters=True)
(graph,) = pydot.graph_from_dot_data(dot_data.getvalue())
Image(graph.create_png())
X_train_nn = X_train_std
X_test_nn = X_test_std
y_train_nn = y_train
y_test_nn = y_test
Die besten Settings wurden mit dem salary als SUT ermittelt. Die Durchlaufzeit verhindert ein Austesten von vielen verschiedenen Setups.
Durch die Verwendung von GridSearch (https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html) ist Cross Validation beispielsweise schon inkludiert.
first = [
(1,), (8,), (16,), (32,), (64,), (128,), (256,), (512,),
(1, 1), (8, 8), (16, 16), (32, 32), (64, 64), (128, 128), (256, 256), (512, 512),
(1, 1, 1), (8, 8, 8), (16, 16, 16), (32, 32, 32), (64, 64, 64), (128, 128, 128), (256, 256, 256), (512, 512, 512),
]
def do_grid_search_for_nn():
param_grid = {
# 'hidden_layer_sizes': first,
# 'activation': ['tanh', 'relu', 'logistic', 'identity']
"hidden_layer_sizes": [(32, 32)], # Best Settings
'activation': ['logistic']
}
mlp = MLPClassifier(max_iter=200)
clf = GridSearchCV(mlp, param_grid, cv=3, scoring='accuracy')
return clf.fit(X_train_nn, y_train_nn)
grid_nn = do_grid_search_for_nn()
def print_outcome_for_nn():
print('best params:\n', grid_nn.best_params_, "\n")
print('best score:\n', round(grid_nn.best_score_ * 100, 2), "\n")
predictions = grid_nn.predict(X_test_nn);
print('predictions:\n', grid_nn.predict(X_test_nn), "\n")
print('confusion matrix:\n', confusion_matrix(y_test_nn, predictions), "\n")
print('classification report:\n', classification_report(y_test_nn, predictions), "\n")
print_outcome_for_nn()
pca = PCA(n_components=2)
pca.fit(X)
X_pca = pca.transform(X)
print(X.shape)
print(x_pca.shape)
y = y.astype(int) # Für categorical_salary
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y[:], edgecolor='none', alpha=0.5, cmap=plt.cm.get_cmap('rainbow', 10))
plt.xlabel('First principal component')
plt.ylabel('Second Principal Component')
plt.colorbar()
pca = PCA().fit(split_df.drop(columns=['salary'])) # Für categorical_salary
plt.plot(np.cumsum(pca.explained_variance_ratio_))
plt.xlabel('number of components')
plt.ylabel('cumulative explained variance');
PCA is mainly used for dimensionality reduction, not for visualization. To visualize high dimension data, we mostly use T-SNE.
def do_tsne(n_iter, perplexity = 25):
time_start = time.time()
tsne = TSNE(n_iter=n_iter, perplexity=perplexity)
tsne_results = tsne.fit_transform(X)
print('time in seconds:', time.time() - time_start, '\n')
print('time in minutes:', (time.time() - time_start) / 60, '\n')
print("iterations:", tsne.n_iter_, '\n')
print("perplexity: ", perplexity, '\n')
print("Kullback-Leibler divergance:", tsne.kl_divergence_, '\n')
plt.scatter(tsne_results[:, 0], tsne_results[:, 1], c=y, edgecolor='none', alpha=0.5, cmap=plt.cm.get_cmap('rainbow', 10))
print(" ========== 250 ITERATIONS - FIRST ========== \n")
do_tsne(250)
print(" ========== 250 ITERATIONS - SECOND ========== \n")
do_tsne(250)
print(" ========== 500 ITERATIONS - FIRST ========== \n")
do_tsne(500)
print(" ========== 500 ITERATIONS - SECOND ========== \n")
do_tsne(500)
print(" ========== 1000 ITERATIONS - FIRST ========== \n")
do_tsne(1000)
print(" ========== 1000 ITERATIONS - SECOND ========== \n")
do_tsne(1000)
print(" ========== 20000 ITERATIONS - FIRST ========== \n")
do_tsne(2000)
print(" ========== 20000 ITERATIONS - SECOND ========== \n")
do_tsne(2000)
def do_kmeans(n_clusters):
data_df = pd.DataFrame(X_pca, columns=['first_pc', 'second_pc'])
kmeans = KMeans(n_clusters=n_clusters, init='k-means++', max_iter=300, n_init=10, random_state=0)
kmeans.fit(data_df)
y_kmeans = kmeans.predict(data_df)
plt.scatter(data_df.first_pc, data_df.second_pc, c=y_kmeans, s=50, cmap='viridis')
plt.show()
do_kmeans(categorical_salary_bins)
def do_spectral_clustering(n_clusters):
data_df = pd.DataFrame(X_pca, columns=['first_pc', 'second_pc'])
spectral_clustering = SpectralClustering(n_clusters=n_clusters, affinity='nearest_neighbors', random_state=0)
y_spectral_clustering = spectral_clustering.fit(data_df)
plt.scatter(data_df.first_pc, data_df.second_pc, c=y_spectral_clustering.labels_, s=50, cmap='viridis')
plt.show()
do_spectral_clustering(categorical_salary_bins)
| SUT | Linear | Logistic | KNN | Decision Tree | Neural Network |
|---|---|---|---|---|---|
| slaray | 51.46 | 28.67 | 2.46 | 6.81 | 2.88 |
| categorical salary | 58.64 | 33.24 | 25.2 | 28.09 | 31.03 |
Die erzielten Ergebnisse, vor allem jene die salary als subject under test (SUT) haben, sind leider sehr enttäuschend. Aufgrund der zahlreichen zur Verfügung gestellten Features hätte man sich ein beseres Ergebnis erwartet.
Durch die Einteilung in bins - also categorical salary - sind die Ergebnisse etwas besser geworden. Wenn man die Bin-Anzahl verringert kann das Ergebnis logischerweise noch etwas verbessert werden. Möglicherweise wäre eine Einteilung in drei bzw. vier Stufen (wenig, mittel, hoch, (top)) auch denkbar.